package database

import (
	"fmt"
	"time"

	"gddgame.cc/galaxy/binocle"
	. "gddgame.cc/galaxy/common/dsl/db"
	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/common/feature/supernova"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils/def"
)

const PluginName = "database"

var (
	RootProjectPath = ""
)

func init() {

	RegisterPlugin(PluginProvider{
		Name: PluginName,
		//Depends: []string{notice.PluginName, report.PluginName},

		OnInit: func(builder composer.Builder, core binocle.CoreClient) error {
			fmt.Println("init")
			core.OnStarted(func() {})

			AddTable(&Table{
				Model: RegionModel,
			})
			AddTable(&Table{
				Model: EnvironmentModel,
			})
			AddTable(&Table{
				Model: SnapshotModel,
			})
			AddTable(&Table{
				Model: RecordModel,
			})
			return nil
		},
		BindRouter: BindRouter,
	})
}

func BindRouter(builder composer.Builder, core binocle.CoreClient, r *SwaggerRouter) {
	// 加载object的表信息
	r.Post("/database/refresh/environment", Swagger{
		Controller: func(context def.Context, environmentId int) error {
			var environment Environment
			if _, err := EnvironmentModel.PK(environmentId).One(&environment); err != nil {
				return SystemException(err)
			}
			if err := refreshEnvironment(&environment); err != nil {
				return ValidException(err)
			}
			return nil
		},
		Params: []Param{
			{Name: "environment_id", In: Body, Type: Int},
		},
		Response: ApiResponse,
	})
	r.Post("/database/clear/environment", Swagger{
		Controller: func(context def.Context, environmentId int) error {
			var environment Environment
			if _, err := EnvironmentModel.PK(environmentId).One(&environment); err != nil {
				return SystemException(err)
			}
			if _, err := SnapshotModel.Query().Where("=", "environment_id", environmentId).Delete(); err != nil {
				return SystemException(err)
			}
			if _, err := RecordModel.Query().Where("=", "environment_id", environmentId).Delete(); err != nil {
				return SystemException(err)
			}
			if _, err := EnvironmentModel.PK(environmentId).Delete(); err != nil {
				return SystemException(err)
			}
			return nil
		},
		Params: []Param{
			{Name: "environment_id", In: Body, Type: Int},
		},
		Response: ApiResponse,
	})
	r.Post("/database/compare/cross", Swagger{
		Controller: func(context def.Context, environmentId int, snapshotId int, refresh bool) (*DiffInfo, error) {
			var environment Environment
			var snapshot Snapshot
			var tableList EnvironmentTableSlice
			var err error
			if _, err := EnvironmentModel.PK(environmentId).One(&environment); err != nil {
				return nil, SystemException(err)
			}
			tableList = environment.Tables
			if refresh {
				tableList, err = refreshEnvironmentTable(&environment)
				if err != nil {
					return nil, ValidException(err)
				}
			}
			if _, err := SnapshotModel.PK(snapshotId).One(&snapshot); err != nil {
				return nil, SystemException(err)
			}
			if snapshot.FinishTime.After(time.Now()) {
				return nil, ValidException("快照还未执行完")
			}
			if snapshot.Process == Fail {
				return nil, ValidException("快照执行失败")
			}
			info := ComputeDiff(tableList.Transform(), snapshot.Tables.Transform())
			return info, nil
		},
		Params: []Param{
			{Name: "environment_id", In: Body, Type: Int},
			{Name: "snapshot_id", In: Body, Type: Int},
			{Name: "refresh", In: Body, Type: Boolean},
		},
		Response: ApiResponse,
	})
	r.Post("/database/compare/snapshot", Swagger{
		Controller: func(context def.Context, firstId int, secondId int) (*DiffInfo, error) {
			var first Snapshot
			var second Snapshot

			if _, err := SnapshotModel.PK(firstId).One(&first); err != nil {
				return nil, SystemException(err)
			}
			if first.FinishTime.After(time.Now()) {
				return nil, ValidException("快照还未执行完")
			}
			if first.Process == Fail {
				return nil, ValidException("快照执行失败")
			}
			if _, err := SnapshotModel.PK(secondId).One(&second); err != nil {
				return nil, SystemException(err)
			}
			if second.FinishTime.After(time.Now()) {
				return nil, ValidException("快照还未执行完")
			}
			if second.Process == Fail {
				return nil, ValidException("快照执行失败")
			}
			info := ComputeDiff(first.Tables.Transform(), second.Tables.Transform())
			return info, nil
		},
		Params: []Param{
			{Name: "first_id", In: Body, Type: Int},
			{Name: "second_id", In: Body, Type: Int},
		},
		Response: ApiResponse,
	})
	r.Post("/database/compare/environment", Swagger{
		Controller: func(context def.Context, firstId int, secondId int, refresh bool) (*DiffInfo, error) {
			var first Environment
			var firstTable EnvironmentTableSlice
			var second Environment
			var secondTable EnvironmentTableSlice
			var err error
			if _, err := EnvironmentModel.PK(firstId).One(&first); err != nil {
				return nil, SystemException(err)
			}
			firstTable = first.Tables
			if refresh {
				firstTable, err = refreshEnvironmentTable(&first)
				if err != nil {
					return nil, ValidException(err)
				}
			}
			if _, err := EnvironmentModel.PK(secondId).One(&second); err != nil {
				return nil, SystemException(err)
			}
			secondTable = second.Tables
			if refresh {
				secondTable, err = refreshEnvironmentTable(&second)
				if err != nil {
					return nil, ValidException(err)
				}
			}
			info := ComputeDiff(firstTable.Transform(), secondTable.Transform())
			return info, nil
		},
		Params: []Param{
			{Name: "first_id", In: Body, Type: Int},
			{Name: "second_id", In: Body, Type: Int},
			{Name: "refresh", In: Body, Type: Boolean},
		},
		Response: ApiResponse,
	})
	r.Post("/database/save", Swagger{
		Controller: SaveEnvironment,
		Params: []Param{
			{Name: "environment_id", In: Body, Type: Int},
			{Name: "remark", In: Body, Type: String},
			{Name: "callback", In: Body},
		},
		Response: ApiResponse,
	})
	r.Post("/database/recovery", Swagger{
		Controller: RecoveryEnvironment,
		Params: []Param{
			{Name: "environment_id", In: Body, Type: Int},
			{Name: "snapshot_id", In: Body, Type: Int},
			{Name: "clear", In: Body, Type: Boolean},
			{Name: "callback", In: Body},
		},
		Response: ApiResponse,
	})
}

func ComputeDiff(source []TableInfo, desc []TableInfo) *DiffInfo {
	info := &DiffInfo{}
	sourceTable := make([]*supernova.Table, len(source))
	for index, table := range source {
		sourceTable[index] = table.GetTable()
	}
	descTable := make([]*supernova.Table, len(desc))
	for index, table := range desc {
		descTable[index] = table.GetTable()
	}
	diffSlice := supernova.NewDiffSlice(sourceTable, descTable)
	for _, diff := range diffSlice {
		oldTable := diff.OldTable()
		if oldTable != nil {
			info.Old = append(info.Old, oldTable.Name)
			continue
		}
		newTable := diff.NewTable()
		if newTable != nil {
			info.New = append(info.New, newTable.Name)
			continue
		}
		tableInfo := TableDiffInfo{
			Name: diff.Source.Name,
		}
		oldColumns, newColumns, changeColumns := diff.Column()
		oldIndexs, newIndexs := diff.Index()
		oldForeign, newForeign := diff.Foreign()
		if oldColumns != nil {
			for _, column := range oldColumns {
				tableInfo.RemoveColumn = append(tableInfo.RemoveColumn, column.Field)
			}
		}
		if newColumns != nil {
			for _, column := range oldColumns {
				tableInfo.AddColumn = append(tableInfo.AddColumn, column.Field)
			}
		}
		if changeColumns != nil {
			for _, column := range changeColumns {
				tableInfo.ChangeColumn = append(tableInfo.ChangeColumn, column.Field)
			}
		}
		if oldIndexs != nil {
			for _, index := range oldIndexs {
				tableInfo.RemoveIndex = append(tableInfo.RemoveIndex, index.Name)
			}
		}
		if newIndexs != nil {
			for _, index := range newIndexs {
				tableInfo.AddIndex = append(tableInfo.AddIndex, index.Name)
			}
		}
		if oldForeign != nil {
			for _, foreign := range oldForeign {
				tableInfo.RemoveForeign = append(tableInfo.RemoveForeign, foreign.Name)
			}
		}
		if newForeign != nil {
			for _, foreign := range newForeign {
				tableInfo.AddForeign = append(tableInfo.AddForeign, foreign.Name)
			}
		}

		info.Table[diff.Source.Name] = tableInfo
	}
	for _, table := range source {
		v, ok := info.Table[table.GetTable().Name]
		if !ok {
			continue
		}
		v.OldRecord = table.GetRecord()
	}
	for _, table := range desc {
		v, ok := info.Table[table.GetTable().Name]
		if !ok {
			continue
		}
		v.NewRecord = table.GetRecord()
	}
	return info
}
